-- 数据库服务
-- Author: Colocaisa
-- Date: 2019-05-16

local skynet = require 'skynet';
require 'skynet.manager';
local pgsql = require 'us.db.pgsql';
local log = require 'log';
local string = require 'us.string';
local table = require 'us.table';

local SEND_SESSION_NO_RET = 0;

-- dao
local db_account = require 'us.dao.db_account';
local db_player = require 'us.dao.db_player';
local db_character = require 'us.dao.db_character';

-- 常用函数引用
local string_split = string.split;

---@class us_db @database服务
local us_db = {}

local pgsql_pool = nil;
local redis_pool = nil;
local us_dbsync = nil;


local CMD = {};
local command = {};


Modules = {}
Modules['cmd'] = CMD


local function register_module(name, dao)
	Modules[name] = dao
    setmetatable(dao, { __index = command })
end

local redis_cmd = {
    ["set"]         = "set",
    ["get"]         = "get",
    ["del"]         = "del",
    ["incr"]        = "incr",
    ["hmset"]       = "hmset",
    ["hmget"]       = "hmget",
    ["hset"]        = "hset",
    ["hget"]        = "hget",
    ["hgetall"]     = "hgetall",
    ["zadd"]        = "zadd",
    ["zrange"]      = "zrange",
    ["zrevrange"]   = "zrevrange",
    ["zrank"]       = "zrank",
    ["zrevrank"]    = "zrevrank",
    ["zscore"]      = "zscore",
    ["zcount"]      = "zcount",
    ["zcard"]       = "zcard",
    ["expire"]      = "expire",
}


-- db结果转换
local convert_result = {};
convert_result["hgetall"] = function (result)
    if not result or #result < 1 then return end
    local r = {}
    for i = 1, #result, 2 do
        local k = result[i];
        local v = result[i+1];
        v = tonumber(v) == nil and string_split(v, '|') or tonumber(v);
        v = v == '*' and {} or v;
        r[k]=v;
    end
    return {r};
end


-- 接口扩展
local function compose_cmd(cmd, ...)
    local args = {...};
    return string.format("SELECT * FROM %s(%s);", cmd, string.format('\'%s\'', table.concat(args, '\',\'')));
end


-- 检查结果是否为空
local function check_null(result)
    for _, record in pairs(result) do
        for _, v in pairs(record) do
            return false;
        end
    end
    return true;
end


local function exec_pgsql(sql)
    local result = skynet.call(pgsql_pool, "lua", "execute", sql);
    if not result or check_null(result) then return end
    -- log.dump(result, "result");

    local t = {}
    for _, v in pairs(result) do
        if "ERROR" == v.severity then
            local msg = tostring(v.severity) .. ": " .. tostring(v.message)
            if v.position then
                msg = tostring(v.msg) .. " (" .. tostring(v.position) .. ")"
            end
            if v.detail then
                msg = tostring(v.msg) .. "\n" .. tostring(v.detail)
            end
            log.error(msg);
            return v;
        end
        table.insert(t, v);
    end
    return t;
end

local function exec_redis(cmd, key, ...)
    -- print(cmd, key);
    assert(string.len(key) > 0, 'invalid redis key ' .. key)
    -- local hash_num = string.byte(key, 1) + string.byte(key, #key)
    local result = skynet.call(redis_pool, "lua", cmd, key, ...);
    if convert_result[cmd] then
        result = convert_result[cmd](result);
    end
    return result
end

setmetatable(command, { __index = function(t,k)
    local cmd = string.lower(k)
    local f = function (self, v, ...)
        if redis_cmd[cmd] then
            -- 执行redis
            log.notice("[REDIS]execute redis sql->%s %s", k, v);
            return exec_redis(cmd, v, ...);
        elseif 'query' == cmd then
            -- query执行sql
            log.notice("[PGSQL]execute pgsql sql->%s", v);
            return exec_pgsql(v);
        else
            -- 执行存储过程
            local sql = compose_cmd(cmd, v, ...);
            log.notice("[PGSQL]execute pgsql sql->%s", sql);
            return exec_pgsql(sql);
        end
	end
    t[k] = f
	return f
end})


function us_db.start()
    pgsql_pool = skynet.uniqueservice("us_pgsql");
    skynet.call(pgsql_pool, "lua", "start");

    redis_pool = skynet.uniqueservice("us_redis");
    skynet.call(redis_pool, "lua", "start");

    -- 注册dao;
    register_module('account',      db_account);
    register_module('player',       db_player);
    register_module('character',    db_character);
end

function on_cmd_err(err)
    log.error(err);
end

function us_db.init()
    skynet.dispatch("lua", function(session, source, module, cmd, ...)
        local mod = Modules[module]
        assert(mod, string.format('no database module found for name [%s].', module));
        
        local f = mod[cmd]
        assert(f and type(f) == 'function', string.format('no command [%s] found ,for module [%s].', cmd, module))

        local function ret (ok, ...)
  			if session ~= SEND_SESSION_NO_RET then
  				skynet.retpack (...)
  			end
        end

        local args = {...}
        -- log.dump(args, "args")
        ret(xpcall(f, on_cmd_err, mod, ...));
    end)
    skynet.register(SERVICE_NAME);
    us_db.start();
end

skynet.start(us_db.init);
